#include <stdint.h>
#include <iostream>
#include <iomanip>

using namespace std;

enum {
// Single-Byte Mode Tag Values
	SQU = 0x0E, // Quote Unicode       ( hbyte, lbyte )
	SCU = 0x0F, // Change to Unicode
	SQ0 = 0x01, // Quote from Window 0 ( byte )
	SC0 = 0x10, // Change to Window 0
	SD0 = 0x18, // Define Window 0     ( byte )
	SDX = 0x0B, // Define Extended     ( hbyte, lbyte )

// Unicode Mode Tag Values
	UQU = 0xF0, // Quote Unicode      ( hbyte, lbyte )
	UC0 = 0xE0, // Change to Window 0
	UD0 = 0xE8, // Define Window 0    ( byte )
	UDX = 0xF1, // Define Extended    ( hbyte, lbyte )
};

static int32_t SCSU_offsets [16] = {
    /* initial offsets for the 8 dynamic (sliding) windows */
    0x0080, /* Latin-1 */
    0x00C0, /* Latin Extended A */
    0x0400, /* Cyrillic */
    0x0600, /* Arabic */
    0x0900, /* Devanagari */
    0x3040, /* Hiragana */
    0x30A0, /* Katakana */
    0xFF00, /* Fullwidth ASCII */

    /* offsets for the 8 static windows */
    0x0000, /* ASCII for quoted tags */
    0x0080, /* Latin - 1 Supplement (for access to punctuation) */
    0x0100, /* Latin Extended-A */
    0x0300, /* Combining Diacritical Marks */
    0x2000, /* General Punctuation */
    0x2080, /* Currency Symbols */
    0x2100, /* Letterlike Symbols and Number Forms */
    0x3000  /* CJK Symbols and punctuation */
};

int OffsetTable[256]; // Window Offset Table

void UTF16_out (uint16_t *& dest, int charcode)
{
	if ( charcode >= 0x10000 )
	{
		UTF16_out ( dest, (charcode >> 10) + 0xD7C0 );
		UTF16_out ( dest, (charcode & 0x3FF) | 0xDC00 );
		return;
	}
	*(dest++) = charcode;
	cout << std::setw(4) << std::setfill('0') << hex << charcode << endl;
}

int decode (const unsigned char * src, uint16_t * dest)
{
	bool isUnicodeMode = false;
	char SCSU_Window = 0;
	
	while (true)
	{
		if (isUnicodeMode)
		{
			if (*src < 0xE0 || *src >= 0xF3)
			{
				UTF16_out(dest, (*src << 8) | src[1]);
				src += 2;
			}
			else switch (*src)
			{
			case 0xF0: // Quote Unicode
				UTF16_out(dest, (src[1] << 8) | src[2]);
				src += 3;
				break;
			case 0xF1: // Define Extended
				isUnicodeMode = false;
				SCSU_Window = src[1] >> 5;
				SCSU_offsets [SCSU_Window] = ((((src[1] << 8) | src[2]) & 0x1FFF) << 7) + 0x10000;
				src += 3;
				break;
			case 0xF2: // Reserved for future use
				break;
			default:
				isUnicodeMode = false;
				SCSU_Window = (*src - UC0) & 7;
				if (*src >= UD0)
					SCSU_offsets [SCSU_Window] = OffsetTable [*++src];
				src++;
			}
		}
		else if (*src < 0x80) // single byte mode
		{
			if (*src >= 0x20 || ((1 << *src) & 0x2600))
				UTF16_out(dest, *(src++));
			else switch (*src)
			{
			case 0: // NULL character = end of string
				return 0;
			case SQU: // Quote Unicode
				UTF16_out(dest, (src[1] << 8) | src[2]);
				src += 3;
				break;
			case SCU: // Change to Unicode
				isUnicodeMode = true; src++;
				break;
			case SDX: // Define Extended
				SCSU_Window = src[1] >> 5;
				SCSU_offsets [SCSU_Window] = ((((src[1] << 8) | src[2]) & 0x1FFF) << 7) + 0x10000;
				src += 3;
				break;
			default:
				if (*src < SQ0 + 8)
				{
					int tmp_window = *src - SQ0;
					int next_byte = src[1];
					UTF16_out(dest, SCSU_offsets[(~next_byte >> 4) & 8 | tmp_window] + (next_byte & 0x7F));
					src += 2;
					break;
				}
				SCSU_Window = (*src - SC0) & 7;
				if (*src >= SD0)
					SCSU_offsets [SCSU_Window] = OffsetTable [*++src];
				src++;
			}
		}
		else
		{
			UTF16_out(dest, SCSU_offsets [SCSU_Window] + *(src++) - 0x80);
		}
	}
}

int main ()
{
	int i;
	for (i = 0x01; i <= 0x67; i++)
		OffsetTable[i] = i * 0x80;
	for (i = 0x68; i <= 0xA7; i++)
		OffsetTable[i] = i * 0x80 + 0xAC00;

	OffsetTable[0xF9] = 0x00C0; // Latin-1 letters + half of Latin Extended-A 
	OffsetTable[0xFA] = 0x0250; // IPA Extensions
	OffsetTable[0xFB] = 0x0370; // Greek
	OffsetTable[0xFC] = 0x0530; // Armenian 
	OffsetTable[0xFD] = 0x3040; // Hiragana
	OffsetTable[0xFE] = 0x30A0; // Katakana
	OffsetTable[0xFF] = 0xFF60; // Halfwidth Katakana

	uint16_t* target = new uint16_t[200];
	
	const unsigned char * src = (const unsigned char*)(
		"\x08\x00\x1B\x4C\xEA\x16\xCA\xD3\x94\x0F\x53\xEF\x61\x1B\xE5\x84"
		"\xC4\x0F\x53\xEF\x61\x1B\xE5\x84\xC4\x16\xCA\xD3\x94\x08\x02\x0F"
		"\x53\x4A\x4E\x16\x7D\x00\x30\x82\x52\x4D\x30\x6B\x6D\x41\x88\x4C"
		"\xE5\x97\x9F\x08\x0C\x16\xCA\xD3\x94\x15\xAE\x0E\x6B\x4C\x08\x0D"
		"\x8C\xB4\xA3\x9F\xCA\x99\xCB\x8B\xC2\x97\xCC\xAA\x84\x08\x02\x0E"
		"\x7C\x73\xE2\x16\xA3\xB7\xCB\x93\xD3\xB4\xC5\xDC\x9F\x0E\x79\x3E"
		"\x06\xAE\xB1\x9D\x93\xD3\x08\x0C\xBE\xA3\x8F\x08\x88\xBE\xA3\x8D"
		"\xD3\xA8\xA3\x97\xC5\x17\x89\x08\x0D\x15\xD2\x08\x01\x93\xC8\xAA"
		"\x8F\x0E\x61\x1B\x99\xCB\x0E\x4E\xBA\x9F\xA1\xAE\x93\xA8\xA0\x08"
		"\x02\x08\x0C\xE2\x16\xA3\xB7\xCB\x0F\x4F\xE1\x80\x05\xEC\x60\x8D"
		"\xEA\x06\xD3\xE6\x0F\x8A\x00\x30\x44\x65\xB9\xE4\xFE\xE7\xC2\x06"
		"\xCB\x82"
	);
	
	decode(src, target);
	
	delete target;
}